<?php
if(! class_exists('WF')){
    require_once (dirname(__FILE__) . '/../init.php');
}
WF::getConfigFactory()->getOpt(); //include WFOpt
require_once(dirname(__FILE__).'/data/DataMigrate.php');
abstract class AMigrate
{
    /**
     * @var WF
     */
    protected $objFrameworkFrom;
    /**
     * @var WF
     */
    protected $objFrameworkTo;
    /**
     * @var WFLogger
     */
    protected $objLogger;
    /**
     * @var WFWidgetPoolFactory
     */
    protected $objWidgetPoolFactory;
    
    protected static $step;
    protected static $instance;
    /**
     * @var WFCollection
     */
    protected $colDataMigrate;
    public function __construct($objFrameworkFrom, $objFrameworkTo){
        $this->objFrameworkFrom = $objFrameworkFrom;
        $this->objFrameworkTo = $objFrameworkTo;
        $this->objLogger = $this->objFrameworkTo->getLoggerUpgradeWF();
        $this->objWidgetPoolFactory = $this->objFrameworkTo->getWidgetPoolFactory();
        $this->colDataMigrate = $this->objFrameworkTo->getCollection();
        $this->initColDataMigrate();
    }
    /**
     * Enter description here ...
     * @param unknown_type $objMigrate
     */
    /*public function setInstance($objMigrate){
        $this = $objMigrate;
    }*/
    /**
     *\brief Description
     * - This method will check product team Widget Framework installation path is existed or not.
     * - If is not existed will raised an exception
     */
    protected function checkFolderWidgetOldExisted(){
        $srcFolderNameWidgetOldInstall = $this->objFrameworkTo->getMigrateFactory()->getFolderNameWidgetOldInstall();
        $strPathWidgetOld = $this->objFrameworkTo->getDocumentRoot() . '/../' . $srcFolderNameWidgetOldInstall;
        if(false === file_exists($strPathWidgetOld)){
            $strMsg = __CLASS__ . '::' . __CLASS__ . '()=>folder /path/to/' . $srcFolderNameWidgetOldInstall . ' is not exists!';
            throw new WFMigrateMigrateException($strMsg, WFOpt::UPGRADE_WF_FAILED_WIDGET_OLD_FOLDER_NOT_EXISTED);
        }
    }
    
    /**
     *\brief Description
     * - This method will get [WF version from] from class name. EX: WFMigrate16to20 => 16
     */
    protected function getWFVersionFromByClassName(){
        $strExtendsClassName = get_class($this); //WFMigrate16to20
        preg_match ( '/^[0-9]+/' , substr($strExtendsClassName,9),$matches);
        $strWFVersionFromClassName = $matches[0]; //16
        return $strWFVersionFromClassName;
    }
    /**
     *\brief Description
     * - This method will get [WF version to] from class name. EX: WFMigrate16to20 => 20
     */
    protected function getWFVersionToByClassName(){
        $strExtendsClassName = get_class($this); //WFMigrate16to20
        preg_match ( '/^[0-9]+/' , substr($strExtendsClassName,13),$matches);
        $strWFVersionToClassName = $matches[0]; //20
        return $strWFVersionToClassName;
    }
    /**
     *\brief Description
     * - This method is a common method for data migrate.
     * - Please defined it by implement method getArrsObjDataMigrate().
     * - EX: 
     * // new WFDataMigration(strTableName, strColPrimaryKeyOfTable, methodUseToConvertWF30ContainerDataToWF40);
     * $objDataMigrate = new WFDataMigration('CONTAINER', 'container_id', 'getArrDataContainer30to40ByArrDataFrom');
     * // callback method after finish the data migrate
     * $objDataMigrate->setFuncNameCallBack('insertDefaultRecordToTableContainer');
     */
    protected function migrateData(){
        //$arrsObjDataMigrate = $this->getArrsObjDataMigrate();
        // each $objDataMigrate defined the setting for data migrate of specific table
        //foreach($arrsObjDataMigrate as $objDataMigrate){
        $colDataMigrate = $this->getColDataMigrate();
        foreach($colDataMigrate as $objDataMigrate){
            $strTableName = $objDataMigrate->getTableName();
            $strColNamePK = $objDataMigrate->getColNamePK();
            // if $strFuncNameConvertArrDataFrom is defined mean each row data need to convert
            // EX: $strFuncNameConvertArrDataFrom = getArrDataContainer30to40ByArrDataFrom
            $strFuncNameConvertArrDataFrom = $objDataMigrate->getFuncNameConvertArrDataFrom();
            // if $assocSkipRecordColNameWithColValue is defined mean specific row data need to skip
            // EX : $assocSkipRecordColNameWithColValue['name'] = 'localhost' => skip tmwf.serverlist.name = 'localhost'
            $assocSkipRecordColNameWithColValue = $objDataMigrate->getAssocSkipRecordColNameWithColValue();
            
            if($objDataMigrate->getIsDataMigrate()){
                $sql = "SELECT * FROM ".$strTableName." ORDER BY ".$strColNamePK;
                $arrsDataFrom = $this->getDataObjectFromForDataMigration()->query($sql, array());
                if(is_array($arrsDataFrom) && $arrsDataFrom){
                    // check INSERT or UPDATE
                    // Scenerio I:
                    // if php /path/to/interface_request.php upgradeWF.php 1.6 3.0 will do below migrate process:
                    // 1. php /path/to/interface_request.php upgradeWF.php 1.6 2.0 ==> [INSERT] table data from WF1.6 to WF2.0 
                    // 2. php /path/to/interface_request.php upgradeWF.php 2.0 3.0 ==> [UPDATE] table data from WF2.0 to WF3.0
                    
                    // Scenerio II:
                    // if php /path/to/interface_request.php upgradeWF.php 2.0 5.0 will do below migrate process:
                    // 1. php /path/to/interface_request.php upgradeWF.php 2.0 3.0 ==> [INSERT] table data from WF2.0 to WF3.0 
                    // 2. php /path/to/interface_request.php upgradeWF.php 3.0 4.0 ==> [UPDATE] table data from WF3.0 to WF4.0
                    // 3. php /path/to/interface_request.php upgradeWF.php 4.0 5.0 ==> [UPDATE] table data from WF4.0 to WF5.0
                    $insertOrUpdateForDataMigration = $this->getInsertOrUpdateForDataMigration();
                    $strQueryFuncName = 'query'.$insertOrUpdateForDataMigration.'ByParameter';
                    foreach($arrsDataFrom as $arrDataFrom){
                        // check is need to skip or not
                        if(is_array($assocSkipRecordColNameWithColValue) && $assocSkipRecordColNameWithColValue){
                            $key = key($assocSkipRecordColNameWithColValue);
                            $value = $assocSkipRecordColNameWithColValue[$key];
                            if($arrDataFrom[$key] == $value){ //if serverlist.name == 'localhost'
                                continue;
                            }
                        }
                        // check is need to convert data of not
                        // if need, use method $strFuncNameConvertArrDataFrom to do the convert
                        // if no need, use original data migrate to new db
                        if($strFuncNameConvertArrDataFrom){
                            $arrDataFrom = $this->$strFuncNameConvertArrDataFrom($arrDataFrom);
                        }
                        
                        $objParameter = $this->objFrameworkTo->getParameter();
                        
                        // if Update row data
                        if($insertOrUpdateForDataMigration == 'Update'){
                            $objParameter->andWhereEqual($strColNamePK, $arrDataFrom[$strColNamePK]); //set WHERE strColPK='intPK'
                            unset($arrDataFrom[$strColNamePK]); // do not update strColPK
                        }
                        
                        $objParameter->appendFrom($strTableName);
                        $objParameter->setAssocData($arrDataFrom);
                        
                        $isSuccess = $this->objFrameworkTo->getDataObject()->$strQueryFuncName($objParameter);
                        if(false === $isSuccess){
                            $msg = __CLASS__ . '::' . __FUNCTION__ . '()=>Failed to Migrate Table tmwf.'.$strTableName.' | queryInfoByParameter : '.$this->objFrameworkTo->getVarLog($this->objFrameworkTo->getDataObject()->queryInfoByParameter($objParameter)).' | DB error:' . $this->objFrameworkTo->getDataObject()->getErrMessage();
                            throw new WFDbDatabaseObjectException($msg, WFOpt::UPGRADE_WF_FAILED_MIGRATE_TABLE);
                        }
                    }
                }
            }
            $strFuncNameCallBack = $objDataMigrate->getFuncNameCallBack();
            if($strFuncNameCallBack){
                $this->$strFuncNameCallBack();
            }
            
            $msg = '>>>>Step '.$this->getStepNoAccumulate().': Success migrate tmwf.'.$strTableName.' data from WF'.$this->getWFVersionFromByClassName().' to WF'.$this->getWFVersionToByClassName().'!';
            $this->objLogger->info($msg);
        }
    }
    /**
     *\brief Description 
     * If upgradeWF.php 1.6 4.0
     * - It will execute below process
     *   1. WFMigrate16to20::execute() => 1.6 == 16, use widget_old WFDataObject, <<INSERT>> data from widget_old16 to widget20
     *   2. WFMigrate20to30::execute() => 1.6 != 20, use widget WFDataObject, <<UPDATE>> data from widget20 to widget20
     *   3. WFMigrate30to40::execute() => 1.6 != 30, use widget WFDataObject, <<UPDATE>> data from widget20 to widget20
     * return WFDatabaseObject
     */
    protected function getDataObjectFromForDataMigration(){
        $ret = $this->objFrameworkFrom->getDataObject(); //default
        $isMigrateWFVersionFromEqualWidgetOldWFVersion = $this->isMigrateWFVersionFromEqualWidgetOldWFVersion();
        if($isMigrateWFVersionFromEqualWidgetOldWFVersion == false){
            $ret = $this->objFrameworkTo->getDataObject();
        }
        return $ret;
    }
    
    /**
     *\brief Description
     * Analize table row data need to INSERT of UPDATE by below logic:
     * If upgradeWF.php 1.6 4.0
     * - It will execute below process
     *   1. WFMigrate16to20::execute() => 1.6 == 16, use widget_old WFDataObject, <<INSERT>> data from widget_old16 to widget40
     *   2. WFMigrate20to30::execute() => 1.6 != 20, use widget WFDataObject, <<UPDATE>> data from widget40 to widget40
     *   3. WFMigrate30to40::execute() => 1.6 != 30, use widget WFDataObject, <<UPDATE>> data from widget40 to widget40
     *   3. WFMigrate30to40::execute() => 1.6 != 40, use widget WFDataObject, <<UPDATE>> data from widget40 to widget40
     * return WFDatabaseObject
     */
    protected function getInsertOrUpdateForDataMigration(){
        $isMigrateWFVersionFromEqualWidgetOldWFVersion = $this->isMigrateWFVersionFromEqualWidgetOldWFVersion();
        $ret = 'Insert'; //default
        if($isMigrateWFVersionFromEqualWidgetOldWFVersion == false){
            $ret = 'Update';
        }
        return $ret;
    }
    /**
     *\brief Description
     * - Check is upgradeWFVersionFrom is equal to WFVersion defined in widget_old
     *   EX : If upgradeWF.php 2.0 4.0
     *   upgradeWFVersionFrom = 2.0
     *   /path/to/widget_old/inc/config.php WFVersion = 2.0
     */
    protected function isMigrateWFVersionFromEqualWidgetOldWFVersion(){
        //If $strExtendsClassName = 'WFMigrate20to30';
        //then $strWFVersionFromClassName = '20';
        $strWFVersionFromClassName = $this->getWFVersionFromByClassName();
        $strWFVersionFromWidgetOld = str_replace('.', '', $this->objFrameworkFrom->getWFVersion());
        return ($strWFVersionFromClassName == $strWFVersionFromWidgetOld) ? true : false;
    }
	/**
     *\brief Desciption
     * This method will check if WFOld tmwf.db is exists or not if dbType=sqlite
     * If not exists return false, else return true
     */
    protected function isWfOldTmwfDbExists(){
        if($this->objFrameworkFrom->getWFVersion() == '1.6'){
            $strPathWfOldTmwfDb = $this->objFrameworkFrom->getDocumentRoot() . '/includes/db/tmwf.db';
        }else{
            $strPathWfOldTmwfDb = $this->objFrameworkFrom->getDocumentRoot() . '/repository/db/sqlite/tmwf.db';
        }
        $isExists = file_exists($strPathWfOldTmwfDb);
        return $isExists;
    }
    /**
     *\brief Desciption
     * This method will check if WF16 tmwf.db is exists or not if dbType=sqlite
     * If not exists raise an exception
     */
    protected function checkIsWfOldTmwfDbExists(){
        $isExists = $this->isWfOldTmwfDbExists();
        if(false === $isExists){
            $msg = __CLASS__ . '::' . __FUNCTION__ . '()=>WfOld /path/to/tmwf.db not exists';
            throw new WFDbDatabaseObjectException($msg);
        }
    }
    /**
     *\brief Desciption
     * This method will check if WF16 tmwf.db is exists or not if dbType=sqlite
     * If not exists raise an exception
     */
    /*protected function checkIsWf16TmwfDbExists(){
        $strPathWf16TmwfDb = $this->objFrameworkFrom->getDocumentRoot() . '/includes/db/tmwf.db';
        $isExists = file_exists($strPathWf16TmwfDb);
        if(false === $isExists){
            $msg = __CLASS__ . '::' . __FUNCTION__ . '()=>WF16 /path/to/includes/db/tmwf.db not exists';
            throw new WFDbDatabaseObjectException($msg);
        }
    }*/
    /**
     *\brief Desciption
     * This method will get accumulate Step number
     */
    protected function getStepNoAccumulate(){
        return ++self::$step;
    }
    /**
     *\brief Desciption
     * This method will get accumulate Step number
     */
    protected function doStepCheckFolderWidgetOldExisted(){
        // This step will skip if already execute
        if($this->isMigrateWFVersionFromEqualWidgetOldWFVersion() === false){
            $msg = ">>>>Step ".$this->getStepNoAccumulate().": ".__FUNCTION__."() already done. Skip ".__FUNCTION__."() process!";
            $this->objLogger->info($msg);
            return;
        }
        //Step2: Start checking is /path/to/widget_old existed!
        $this->objFrameworkTo->getMigrateFactory()->getMigrate()->checkFolderWidgetOldExisted();
        $msg = '>>>>Step '.$this->getStepNoAccumulate().': Start checking is /path/to/widget_old existed!';
        $this->objLogger->info($msg);
    }
    /**
     *\brief Desciption
     * This method handler the relative WF DB process for upgrade  
     */
    protected function doStepCheckWFDB(){
        // This step will skip if already execute
        if($this->isMigrateWFVersionFromEqualWidgetOldWFVersion() === false){
            $msg = ">>>>Step ".$this->getStepNoAccumulate().": ".__FUNCTION__."() already done. Skip ".__FUNCTION__."() process!";
            $this->objLogger->info($msg);
            return;
        }
        // StepX: checking WF database type, EX: sqlite / mysql
        $strDatabaseType = $this->objFrameworkTo->getConfigFactory()->getConfig()->getDbConfigType();
        $msg = '>>>>Step '.$this->getStepNoAccumulate().': Success get WF database type==>DBType:[' . $strDatabaseType . ']';
        $this->objLogger->info($msg);
        switch($strDatabaseType){
            case 'sqlite':
                //StepX-1: If sqlite, check if /path/to/includes/db/tmwf.db is exists
                $this->checkIsWfOldTmwfDbExists();
                break;
            case 'mysql':
                //StepX-1: If mysql, rename database tmwf to tmwf_old!
                //rename tmwf to tmwf_old
                //If you use a MySQL version >=5.1 
                //$sql = "RENAME DATABASE `tmwf` TO `tmwf_old`";
                //$isSuccess = $this->objFrameworkTo->getDataObject('')->query($sql);
                $sql = "DROP DATABASE IF EXISTS tmwf_old";
                $isSuccess = $this->objFrameworkTo->getDataObject('')->query($sql);
                if(false === $isSuccess){
                    $msg = __CLASS__ . '::' . __FUNCTION__ . '()=>Failed to drop database tmwf_old!';
                    throw new WFMigrateMigrateException($msg, WFOpt::UPGRADE_WF_FAILED_DROP_DATABASE_TMWF_OLD);
                }
                $msg = '>>>>Step '.$this->getStepNoAccumulate().': Success DROP DATABASE IF EXISTS tmwf_old!';
                $this->objLogger->info($msg);
                $sql = "CREATE DATABASE IF NOT EXISTS tmwf_old DEFAULT CHARACTER SET = utf8";
                $isSuccess = $this->objFrameworkTo->getDataObject('')->query($sql);
                if(false === $isSuccess){
                    $msg = __CLASS__ . '::' . __FUNCTION__ . '()=>Failed to CREATE DATABASE tmwf_old!';
                    throw new WFMigrateMigrateException($msg, WFOpt::UPGRADE_WF_FAILED_CREATE_DATABASE_TMWF_OLD);
                }
                $msg = '>>>>Step '.$this->getStepNoAccumulate().': Success CREATE DATABASE tmwf_old!';
                $this->objLogger->info($msg);
                $arrTableName = $this->objFrameworkTo->getConfigFactory()->getOpt()->getWFTableName();
                foreach($arrTableName as $strTableName){
                    $isTableExisted = $this->objFrameworkTo->getDataObject('tmwf')->isTableExists($strTableName);
                    if(true === $isTableExisted){
                        $sql = "RENAME TABLE tmwf." . $strTableName . " TO tmwf_old." . $strTableName;
                        $isSuccess = $this->objFrameworkTo->getDataObject('tmwf_old')->query($sql);
                        $this->objFrameworkTo->getLogger()->debug('$isSuccess:[' . $isSuccess . ']');
                        if(false === $isSuccess){
                            $msg = __CLASS__ . '::' . __FUNCTION__ . "()=>Failed to RENAME TABLE tmwf." . $strTableName . " TO tmwf_old." . $strTableName;
                            throw new WFMigrateMigrateException($msg, WFOpt::UPGRADE_WF_FAILED_RENAME_TABLE_FROM_CURRENTDB_TO_OLDDB);
                        }
                        $msg = ">>>>Step '.$this->getStepNoAccumulate().': Success RENAME TABLE tmwf." . $strTableName . " TO tmwf_old." . $strTableName;
                        $this->objLogger->info($msg);
                    }
                }
                
                $this->objLogger->info($msg);
                break;
            default:
                $msg = __CLASS__ . '::' . __FUNCTION__ . '()=>Failed to regconized $strDatabaseType:' . $strDatabaseType;
                throw new WFMigrateMigrateException($msg);
                break;
        }
    }
    /**
     *\brief Desciption
     * This method execute php /path/to/interface_request.php initWFDBSchema.php  
     */
    protected function doStepInitWFDBSchema(){
        // This step will skip if already execute
        if($this->isMigrateWFVersionFromEqualWidgetOldWFVersion() === false){
            $msg = '>>>>Step '.$this->getStepNoAccumulate().': '.__FUNCTION__.'() already done. Skip '.__FUNCTION__.'() process!';
            $this->objLogger->info($msg);
            return;
        }
        
        $arrData = array();
        $arrData['file_interface'] = 'initWFDBSchema.php';
        if($this->objFrameworkTo->isUnitTest()){
            $arrData['IS_UNITTEST'] = '1';
        }
        $isSuccess = $this->objFrameworkTo->getWidgetFactory()->getInterface()->execCmdInterfaceByArrData($arrData);
        if(false === $isSuccess){
            $msg = __CLASS__ . '::' . __FUNCTION__ . '()=>Failed to execute php /path/to/interface_request.php initWFDBSchema.php!';
            throw new WFDbDatabaseObjectException($msg, WFOpt::UPGRADE_WF_FAILED_EXEC_INTERFACE_INIT_WFDBSCHEMA);
        }
        
        $msg = '>>>>Step '.$this->getStepNoAccumulate().': Success execute php /path/to/interface_request.php initWFDBSchema.php!';
        $this->objLogger->info($msg);
    }
    /**
     *\brief Desciption
     * This method execute php /path/to/interface_request.php synWidgetXMLToDB.php  
     */
    protected function doStepSynWidgetXMLToDB(){
        // This step will skip if already execute
        if($this->isMigrateWFVersionFromEqualWidgetOldWFVersion() === false){
            $msg = '>>>>Step '.$this->getStepNoAccumulate().': '.__FUNCTION__.'() already done. Skip '.__FUNCTION__.'() process!';
            $this->objLogger->info($msg);
            return;
        }
        
        $arrData = array();
        $arrData['file_interface'] = 'synWidgetXMLToDB.php';
        if($this->objFrameworkTo->isUnitTest()){
            $arrData['IS_UNITTEST'] = '1';
        }
        
        try{
            $this->objFrameworkTo->getWidgetFactory()->getInterface()->synWidgetXMLToDB();
        }catch(Exception $e){
            $msg = __CLASS__ . '::' . __FUNCTION__ . '()=>Failed to execute php /path/to/interface_request.php synWidgetXMLToDB.php!';
            throw new WFDbDatabaseObjectException($msg, WFOpt::UPGRADE_WF_FAILED_EXEC_INTERFACE_SYN_WIDGETXML_TODB);
        }
        $msg = '>>>>Step '.$this->getStepNoAccumulate().': Success execute php /path/to/interface_request.php synWidgetXMLToDB.php';
        $this->objLogger->info($msg);
    }
    /**
     *\brief Desciption
     * This method execute php /path/to/interface_request.php synServerListType.php update  
     */
    protected function doStepSynServerListType(){
        // This step will skip if already execute
        if($this->isMigrateWFVersionFromEqualWidgetOldWFVersion() === false){
            $msg = '>>>>Step '.$this->getStepNoAccumulate().': '.__FUNCTION__.'() already done. Skip '.__FUNCTION__.'() process!';
            $this->objLogger->info($msg);
            return;
        }
        $arrData = array();
        $arrData['file_interface'] = 'synServerListType.php';
        $arrData['filter_condition'] = 'update';
        if($this->objFrameworkTo->isUnitTest()){
            $arrData['IS_UNITTEST'] = '1';
        }
        try{
            $this->objFrameworkTo->getWidgetFactory()->getInterface()->synServerListTypeUpdate();
        }catch(Exception $e){
            $msg = __CLASS__ . '::' . __FUNCTION__ . '()=>Failed to execute php synServerListType.php update!';
            throw new WFDbDatabaseObjectException($msg, WFOpt::UPGRADE_WF_FAILED_EXEC_INTERFACE_SYN_SERVERLISTTYPE);
        }
        $msg = '>>>>Step '.$this->getStepNoAccumulate().': Success execute php synServerListType.php update';
        $this->objLogger->info($msg);
    }
    /**
     *\brief Desciption
     * This method execute php /path/to/interface_request.php synWidgetComponentXMLToDB.php
     */
    protected function doStepSynWidgetComponentXMLToDB(){
        // This step will skip if already execute
        if($this->isMigrateWFVersionFromEqualWidgetOldWFVersion() === false){
            $msg = '>>>>Step '.$this->getStepNoAccumulate().': '.__FUNCTION__.'() already done. Skip '.__FUNCTION__.'() process!';
            $this->objLogger->info($msg);
            return;
        }
        
        $arrData = array();
        $arrData['file_interface'] = 'synWidgetComponentXMLToDB.php';
        if($this->objFrameworkTo->isUnitTest()){
            $arrData['IS_UNITTEST'] = '1';
        }
        try{
            $this->objFrameworkTo->getWidgetFactory()->getInterface()->synWidgetComponentXMLToDB();
        }catch(Exception $e){
            $msg = __CLASS__ . '::' . __FUNCTION__ . '()=>Failed to execute php /path/to/interface_request.php synWidgetComponentXMLToDB.php!';
            throw new WFDbDatabaseObjectException($msg, WFOpt::UPGRADE_WF_FAILED_EXEC_INTERFACE_SYN_WIDGETCOMPONENTXML_TODB);
        }
        $msg = '>>>>Step '.$this->getStepNoAccumulate().': Success execute php /path/to/interface_request.php synWidgetComponentXMLToDB.php';
        $this->objLogger->info($msg);
    }
    abstract public function execute();
    /**
     *\brief Description
     * - This method is a method for get all $objDataMigrate.
     * - $objDataMigrate will define specify data migrate action for each table.
     * - EX: 
     * // new WFDataMigration(strTableName, strColPrimaryKeyOfTable, methodUseToConvertWF30ContainerDataToWF40);
     * $objDataMigrate = new WFDataMigration('CONTAINER', 'container_id', 'getArrDataContainer30to40ByArrDataFrom');
     * // callback method after finish the data migrate
     * $objDataMigrate->setFuncNameCallBack('insertDefaultRecordToTableContainer');
     */
    //abstract protected function getArrsObjDataMigrate();
    protected function getColDataMigrate(){
        return $this->colDataMigrate;
    }
    abstract protected function initColDataMigrate();
}